from manim import *
import random


class Bubble(VGroup):
    file_name: str = "PiCreature/Bubbles_speech.svg"

    bubble_center_adjustment_factor = 0.125

    def __init__(
        self,
        content: str | VMobject | None = None,
        buff: float = 1.0,
        filler_shape=(3.0, 2.0),
        pin_point=None,
        direction=LEFT,
        add_content: bool = True,
        fill_color: ManimColor = BLACK,
        fill_opacity: float = 0.8,
        stroke_color: ManimColor = WHITE,
        stroke_width: float = 3.0,
        **kwargs
    ):
        super().__init__(**kwargs)
        self.direction = direction

        if content is None:
            width, height = filler_shape
            content = Rectangle(width=width, height=height)
            content.set_fill(opacity=0)
            content.set_stroke(width=0)
        elif isinstance(content, str):
            content = Text(content)
        self.content = content

        self.body = self.get_body(content, direction, buff)
        self.body.set_fill(fill_color, fill_opacity)
        self.body.set_stroke(stroke_color, stroke_width)
        self.add(self.body)

        if add_content:
            self.add(self.content)

        if pin_point is not None:
            self.pin_to(pin_point)

    def get_body(self, content: VMobject, direction, buff: float) -> VMobject:
        body = SVGMobject(self.file_name)
        if direction[0] > 0:
            body.flip()
        # Resize
        width = content.get_width()
        height = content.get_height()
        target_width = width + min(buff, height)
        target_height = 1.35 * (height + buff)  # Magic number?
        body.set_width(target_width)
        body.set_height(target_height)
        body.move_to(content)
        body.shift(self.bubble_center_adjustment_factor * body.get_height() * DOWN)
        return body

    def get_tip(self):
        return self.get_critical_point(DOWN + self.direction)

    def get_bubble_center(self):
        factor = self.bubble_center_adjustment_factor
        return self.get_center() + factor * self.get_height() * UP

    def move_tip_to(self, point):
        self.shift(point - self.get_tip())
        return self

    def flip(self, axis=UP, only_body=True, **kwargs):
        super().flip(axis=axis, **kwargs)
        if only_body:
            # Flip in place, don't use kwargs
            self.content.flip(axis=axis)
        if abs(axis[1]) > 0:
            self.direction = -np.array(self.direction)
        return self

    def pin_to(self, mobject, auto_flip=False):
        mob_center = mobject.get_center()
        want_to_flip = np.sign(mob_center[0]) != np.sign(self.direction[0])
        if want_to_flip and auto_flip:
            self.flip()
        boundary_point = mobject.get_critical_point(UP - self.direction)
        vector_from_center = 1.0 * (boundary_point - mob_center)
        self.move_tip_to(mob_center + vector_from_center)
        return self

    def position_mobject_inside(self, mobject, buff=MED_LARGE_BUFF):
        mobject.scale_to_fit_height(self.body.get_height() / 1.5 - 2 * buff)
        mobject.scale_to_fit_width(self.body.get_width() - 2 * buff)
        mobject.shift(self.get_bubble_center() - mobject.get_center())
        return mobject

    def add_content(self, mobject):
        self.position_mobject_inside(mobject)
        self.content = mobject
        return self.content

    def write(self, text):
        self.add_content(Text(text))
        return self

    def resize_to_content(self, buff=1.0):  # TODO
        self.body.match_points(self.get_body(self.content, self.direction, buff))

    def clear(self):
        self.remove(self.content)
        return self


class SpeechBubble(Bubble):
    """表示对话的气泡"""

    def __init__(
        self,
        content=None,
        buff=MED_SMALL_BUFF,
        filler_shape=(2.0, 1.0),
        stem_height_to_bubble_height=0.5,
        stem_top_x_props=(0.2, 0.3),
        **kwargs
    ):
        self.stem_height_to_bubble_height = stem_height_to_bubble_height
        self.stem_top_x_props = stem_top_x_props
        super().__init__(content, buff, filler_shape, **kwargs)

    def get_body(self, content: VMobject, direction, buff: float) -> VMobject:
        rect = SurroundingRectangle(content, buff=buff)
        rect.round_corners()
        lp = rect.get_corner(DL)
        rp = rect.get_corner(DR)
        stem_height = self.stem_height_to_bubble_height * rect.get_height()
        low_prop, high_prop = self.stem_top_x_props
        triangle = Polygon(
            interpolate(lp, rp, low_prop),
            interpolate(lp, rp, high_prop),
            lp + stem_height * DOWN,
        )
        result = Union(rect, triangle)
        result.insert_n_curves(20)
        if direction[0] > 0:
            result.flip()

        return result


class ThoughtBubble(Bubble):
    """表示思考的气泡"""

    def __init__(
        self,
        content=None,
        buff=SMALL_BUFF,
        filler_shape=(2.0, 1.0),
        bulge_radius=0.35,
        bulge_overlap=0.25,
        noise_factor=0.1,
        circle_radii=[0.1, 0.15, 0.2],
        **kwargs
    ):
        self.bulge_radius = bulge_radius
        self.bulge_overlap = bulge_overlap
        self.noise_factor = noise_factor
        self.circle_radii = circle_radii
        super().__init__(content, buff, filler_shape, **kwargs)

    def get_body(self, content: VMobject, direction, buff: float) -> VMobject:
        # 使用椭圆作为基础形状
        width = content.get_width() + 2 * buff
        height = content.get_height() + 2 * buff
        ellipse = Ellipse(width=width, height=height)
        ellipse.move_to(content.get_center())

        # 增大基础半径 - 从15%增加到25%
        base_radius = min(width, height) * 0.25  # 增加基础半径比例
        radius = max(self.bulge_radius, base_radius)

        nf = self.noise_factor

        # 适当减少点的数量，让每个突起更大更明显
        num_points = max(12, int(width))  # 减少点的数量
        points = []

        # 生成点位置
        for theta in np.linspace(0, TAU, num_points):
            # 基础位置在椭圆上
            ellipse_point = np.array(
                [width / 2 * np.cos(theta), height / 2 * np.sin(theta), 0]
            )

            # 将点向内移动一点 (0.85表示向内移动15%)
            inward_factor = 0.85  # 圆心向内移动的因子
            base_point = ellipse_point * inward_factor + ellipse.get_center() * (
                1 - inward_factor
            )

            # 添加随机噪声，顶部和底部的噪声更大
            noise_scale = 1.0 + 0.5 * abs(np.sin(theta))
            noise = nf * radius * noise_scale * (random.random() - 0.1)  # 更偏向外部

            # 最终点 = 基础点 + 随机位移
            point = base_point + ellipse_point * (noise / get_norm(ellipse_point))
            points.append(point)

        # 创建更大的突起
        bulges = VGroup(
            *[
                Circle(
                    radius=radius
                    * (1.2 + 0.4 * random.random())  # 增大基础圆大小并降低随机性
                    * (1 + 0.5 * abs(point[1] - ellipse.get_center()[1]) / height)
                ).move_to(point)
                for point in points
            ]
        )

        cloud = Union(ellipse, *bulges)
        cloud.set_stroke(WHITE, 2)

        # 小气泡的部分也相应调整
        small_circle_scale = max(1.0, min(width, height) / 8)  # 增大小气泡
        circles = VGroup(
            *[Circle(radius=r * small_circle_scale) for r in self.circle_radii]
        )
        circ_buff = 0.25 * self.circle_radii[0] * small_circle_scale
        circles.arrange(UR, buff=circ_buff)
        circles[1].shift(circ_buff * DR)
        circles.next_to(cloud, DOWN, 4 * circ_buff, aligned_edge=LEFT)
        circles.set_stroke(WHITE, 2)

        result = VGroup(*circles, cloud)

        if direction[0] > 0:
            result.flip()

        return result

    def get_tip(self):
        # 思考气泡的尖端是最小的圆
        return self.body[0].get_center()


# 以下类是为了向后兼容旧版API
class OldSpeechBubble(Bubble):
    file_name: str = "PiCreature/Bubbles_speech.svg"


class DoubleSpeechBubble(Bubble):
    file_name: str = "PiCreature/Bubbles_double_speech.svg"


class OldThoughtBubble(Bubble):
    file_name: str = "PiCreature/Bubbles_thought.svg"

    # def get_tip(self):
    #     return self.body[0].get_center()

    def get_body(self, content: VMobject, direction, buff: float) -> VMobject:
        body = super().get_body(content, direction, buff)
        body.sort(lambda p: p[1])
        return body

    def make_green_screen(self):
        self.body[-1].set_fill(GREEN, opacity=1)
        return self
